using System;
using System.Collections;
using System.Collections.Specialized;
using Refly.CodeDom;
using Refly.CodeDom.Statements;
using Refly.CodeDom.Collections;
using Refly.Templates;
using Refly.CodeDom.Expressions;
using System.Text;
using System.Text.RegularExpressions;
using Doubler.Common;
using Reflector.Framework;
using Doubler.Data;
using Doubler.Helpers;
using Doubler.Helpers.Graph;

namespace Doubler.Templates.Fake
{
    public class FakeTemplate : SingleTemplateBase
    {
        
#region Constants

        private const string FakeReturnsPropertyName = "FakeReturns";

#endregion

#region Field

        private IClassDeclaration _FakeClass;
        private IClassDeclaration _FakeReturnClass;
        private ExpressionCollection _ReturnValueProperties;

#endregion
        
#region Properties

        private IClassDeclaration FakeReturnClass
        {
            get
            {
                return _FakeReturnClass;
            }
            set
            {
                _FakeReturnClass = value;
            }
        }

        private ExpressionCollection ReturnValueProperties
        {
            get
            {
                if (_ReturnValueProperties == null)
                    _ReturnValueProperties = new ExpressionCollection();
                return _ReturnValueProperties;
            }
            set
            {
                _ReturnValueProperties = value;
            }
        }

        public override string FileName
        {
            get
            {
                return string.Format(base.NameFormat, this.Subject.Name);
            }
        }

        public override string OutputFilePath
        {
            get
            {
                string NamespacePath = Regex.Replace(this.Namespace, @"\.", "/");
                return string.Format("{0}/{1}/{2}.{3}", this.OutputPath, NamespacePath, this.FileName, this.OutputLanguage);
            }
        }

        private IClassDeclaration FakeClass
        {
            get
            {
                return _FakeClass;
            }
            set
            {
                _FakeClass = value;
            }
        }

#endregion
                
#region Constructor

        public FakeTemplate()
            : base("", "Fake{0}")
        {
            
        }

        public FakeTemplate(CodeLanguage language, string outDirectoryPath, Reflector.CodeModel.ITypeDeclaration subject, ReflectorServices reflectorServices)
            : base(language, outDirectoryPath, subject, "Fake{0}", reflectorServices)
        {
            
        }

#endregion
        
#region Generation Control Flow

        public override void Generate()
        {
            this.ReturnValueProperties = null;
            this.Prepare();
            this.GenerateFake();
            this.Compile();
        }

        public void GenerateFake()
        {
            this.FakeClass = base.NamespaceDeclaration.AddClass(this.FileName);
            if (this.Subject.Interface)
            {
                this.FakeClass.Interfaces.Add(new StringTypeDeclaration(MetaHelper.GetFullTypeName(this.Subject)));
                this.GenerateFakeFromInterfaces(this.Subject);
            }
            else
            {
                this.FakeClass.Parent = new StringTypeDeclaration(MetaHelper.GetFullTypeName(this.Subject));
            }
            this.Imports.Add(typeof(DoubleAttribute).Namespace);
            this.FakeClass.CustomAttributes.Add(typeof(DoubleAttribute));
            this.FakeClass.CustomAttributes.Add(typeof(FakeAttribute));
            this.GenerateFakeReturns();
            this.GenerateFakeConstructors(this.Subject);
            this.GenerateFakeProperties(this.Subject);
            this.GenerateFakeMethods(this.Subject);
            this.GenerateFakeEvents(this.Subject);
        }

        private void GenerateFakeFromInterfaces(Reflector.CodeModel.ITypeDeclaration subject)
        {
            foreach (Reflector.CodeModel.ITypeReference Interface in subject.Interfaces)
            {
                this.GenerateFakeProperties(Interface.Resolve());
                this.GenerateFakeMethods(Interface.Resolve());
                this.GenerateFakeFromInterfaces(Interface.Resolve());
            }
        }

        public void GenerateFakeReturns()
        {
            this.FakeReturnClass = this.FakeClass.AddClass(string.Format("{0}{1}", this.Subject.Name, FakeReturnsPropertyName));
            PropertyFieldPair Pair = GenerationHelper.GeneratePropertyAndField(this.FakeClass, FakeReturnsPropertyName, FakeReturnClass, true, true, System.CodeDom.MemberAttributes.Public);
            Pair.Property.Get.Insert(0, Stm.IfNull(Expr.This.Field(Pair.Field), Stm.Assign(Expr.This.Field(Pair.Field), Expr.New(FakeReturnClass))));
        }

#endregion

#region Constructor Generation

        private void GenerateFakeConstructors(Reflector.CodeModel.ITypeDeclaration subject)
        {
            foreach (Reflector.CodeModel.IMethodDeclaration Constructor in this.Subject.Methods)
            {
                if (Constructor.Name.EndsWith(".ctor"))
                    this.GenerateFakeConstructor(subject, Constructor);
            }
            if (this.Subject.Interface)
            {
                foreach (Reflector.CodeModel.IMethodDeclaration Constructor in this.Services.Search.FindType("System.Object").Methods)
                {
                    if (Constructor.Name.EndsWith(".ctor"))
                        this.GenerateFakeConstructor(subject, Constructor);
                }
            }
        }

        private void GenerateFakeConstructor(Reflector.CodeModel.ITypeDeclaration subject, Reflector.CodeModel.IMethodDeclaration constructorInfo)
        {
            IConstructorDeclaration Constructor = this.FakeClass.AddConstructor();

            foreach (Reflector.CodeModel.IParameterDeclaration ParameterInformation in constructorInfo.Parameters)
            {
                ParameterDeclaration Parameter;
                Refly.CodeDom.StringTypeDeclaration ParameterType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(ParameterInformation.ParameterType));
                Parameter = Constructor.Signature.Parameters.Add(ParameterType, ParameterInformation.Name);
                Constructor.BaseContructorArgs.Add(Expr.Arg(Parameter));
            }
        }
        
#endregion

#region Method Generation

        private void GenerateFakeMethods(Reflector.CodeModel.ITypeDeclaration subject)
        {
            foreach (Reflector.CodeModel.IMethodDeclaration MethodInformation in this.Subject.Methods)
            {
                if (MethodInformation.Abstract
                        && MetaHelper.IsNotNestedMember(MethodInformation))
                    this.GenerateFakeMethod(subject, MethodInformation);
            }
        }

        private void GenerateFakeMethod(Reflector.CodeModel.ITypeDeclaration subject, Reflector.CodeModel.IMethodDeclaration methodInformation)
        {
            MethodDeclaration Method = this.FakeClass.AddMethod(methodInformation.Name);
            Method.Signature.ReturnType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(methodInformation.ReturnType.Type));

            foreach (Reflector.CodeModel.IParameterDeclaration ParameterInformation in methodInformation.Parameters)
            {
                Refly.CodeDom.StringTypeDeclaration ParameterType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(ParameterInformation.ParameterType));
                Method.Signature.Parameters.Add(ParameterType, ParameterInformation.Name);
            }

            System.CodeDom.MemberAttributes VisabilityAttribute = System.CodeDom.MemberAttributes.Public;

            if (subject.Interface)
            {
                Method.ImplementationTypes.Add(new StringTypeDeclaration(MetaHelper.GetFullTypeName(subject)));
            }
            else
            {
                if ((methodInformation.Visibility & Reflector.CodeModel.MethodVisibility.Family) == Reflector.CodeModel.MethodVisibility.Family)
                    VisabilityAttribute = System.CodeDom.MemberAttributes.Family;

                VisabilityAttribute = VisabilityAttribute | System.CodeDom.MemberAttributes.Override;
            }

            Method.Attributes = VisabilityAttribute;

            if (methodInformation.ReturnType.Type.ToString() != "Void" && methodInformation.ReturnType.Type.ToString() != "System.Void")
            {
                Expression FakeReturns = Expr.This.Prop(FakeReturnsPropertyName);
                string ReturnValuePropertyName = string.Format("{0}ReturnValue", this.GetFakeReturnPropertyName(methodInformation, methodInformation.Name));
                Expression Return = FakeReturns.Prop(ReturnValuePropertyName);
                Statement ReturnStatement = Stm.Return(Return);
                Method.Body.Add(ReturnStatement);
                GenerationHelper.GeneratePropertyAndField(this.FakeReturnClass, ReturnValuePropertyName, (Reflector.CodeModel.ITypeReference)methodInformation.ReturnType.Type, true, true, System.CodeDom.MemberAttributes.Public);
            }
        }

        public String GetFakeReturnPropertyName(Reflector.CodeModel.IMethodDeclaration method, string name)
        {
            StringBuilder OverloadBuilder = new StringBuilder();
            foreach (Reflector.CodeModel.IParameterDeclaration ParameterInformation in method.Parameters)
            {
                String Param = String.Empty;
                if (ParameterInformation.ParameterType is Reflector.CodeModel.ITypeReference)
                {
                    Param = ((Reflector.CodeModel.ITypeReference)ParameterInformation.ParameterType).Name;
                }
                else
                {
                    Param = ParameterInformation.ParameterType.ToString();
                }
                Param = Regex.Replace(Param, @"\&", "");
                Param = Regex.Replace(Param, @"\@", "");
                Param = Regex.Replace(Param, @"\[\]", "Array");
                OverloadBuilder.Append(Param);
            }
            string Overloaded = OverloadBuilder.ToString();
            String RecordingName = string.Format("{0}{1}", name, Overloaded);
            return RecordingName;
        }

#endregion

#region Event Generation

        public void GenerateFakeEvents(Reflector.CodeModel.ITypeDeclaration subject)
        {
            foreach (Reflector.CodeModel.IEventDeclaration EventInformation in subject.Events)
            {
                this.GenerateFakeEvent(subject, EventInformation);
            }
        }

        public void GenerateFakeEvent(Reflector.CodeModel.ITypeDeclaration subject, Reflector.CodeModel.IEventDeclaration eventInformation)
        {
            EventDeclaration Event = this.FakeClass.AddEvent(new StringTypeDeclaration(MetaHelper.GetFullTypeName(eventInformation.EventType)), eventInformation.Name);

            string FireMethodName = string.Format("Fire{0}", eventInformation.Name);
            MethodDeclaration FireMethod = this.FakeClass.AddMethod(FireMethodName);
            foreach (Reflector.CodeModel.IParameterDeclaration ParameterInformation in MetaHelper.GetInvokeMethod(eventInformation).Parameters)
            {
                Refly.CodeDom.StringTypeDeclaration ParameterType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(ParameterInformation.ParameterType));
                FireMethod.Signature.Parameters.Add(ParameterType, ParameterInformation.Name);
            }
            DelegateInvokeExpression InvokedDelegate = Expr.DelegateInvoke(Expr.This.Event(Event), FireMethod.Signature.ArgumentReferences);
            FireMethod.Body.Add(InvokedDelegate);
        }
        
#endregion

#region Property Generation

        private void GenerateFakeProperties(Reflector.CodeModel.ITypeDeclaration subject)
        {
            string PropertyName;
            Reflector.CodeModel.ITypeDeclaration PropertyType;
            bool Read;
            bool Write;
            Reflector.CodeModel.MethodVisibility Visability;
            System.CodeDom.MemberAttributes VisabilityAttribute = System.CodeDom.MemberAttributes.Public;
            foreach (Reflector.CodeModel.IPropertyDeclaration PropertyInformation in subject.Properties)
            {
                Read = false;
                Write = false;

                Visability = Reflector.CodeModel.MethodVisibility.Public;

                GenerationHelper.GetSetterInfo(ref Write, ref Visability, PropertyInformation);

                GenerationHelper.GetGetterInfo(ref Read, ref Visability, PropertyInformation);

                if (Read || Write)
                {
                    PropertyName = PropertyInformation.Name;
                    PropertyType = ((Reflector.CodeModel.ITypeReference)PropertyInformation.PropertyType).Resolve();

                    VisabilityAttribute = System.CodeDom.MemberAttributes.Public;

                    if (!subject.Interface)
                    {
                        if ((Visability & Reflector.CodeModel.MethodVisibility.Family) == Reflector.CodeModel.MethodVisibility.Family)
                            VisabilityAttribute = System.CodeDom.MemberAttributes.Family;

                        VisabilityAttribute = VisabilityAttribute | System.CodeDom.MemberAttributes.Override;
                    }

                    PropertyFieldPair Pair = GenerationHelper.GeneratePropertyAndField(this.FakeClass, PropertyName, PropertyType, Read, Write, VisabilityAttribute);

                    if (subject.Interface)
                    {
                        Pair.Property.ImplementationTypes.Add(new StringTypeDeclaration(MetaHelper.GetFullTypeName(subject)));
                    }

                    if (!Write)
                    {
                        PropertyName = String.Format("Set{0}", PropertyInformation.Name);

                        GenerationHelper.GeneratePropertyAgainstField(this.FakeClass, PropertyName, PropertyType, Read, true, VisabilityAttribute, Pair.Field);
                    }
                    if (!Read)
                    {
                        PropertyName = String.Format("Get{0}", PropertyInformation.Name);

                        GenerationHelper.GeneratePropertyAgainstField(this.FakeClass, PropertyName, PropertyType, true, Write, VisabilityAttribute, Pair.Field);
                    }
                }
            }
        }
        
#endregion

    }
}
